package run.yiqi.blog.config;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Service;

import lombok.extern.slf4j.Slf4j;
import run.yiqi.blog.dao.EndUserDao;
import run.yiqi.blog.model.EndUser;
import run.yiqi.blog.service.WorkFlowService;

@Slf4j
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class MultiHttpSecurityConfig {

    @Order(1)
    @Configuration
    public class UserWebSecurityConfig extends WebSecurityConfigurerAdapter {
        @Lazy
        @Autowired
        private MyUserDetailsService uds;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    // 请求管理API时,都要求登录鉴权.
                    //, "/swagger-ui.html"
                    .antMatchers("/api/admin/**").authenticated()
                    .and()
                    .exceptionHandling()
                    .authenticationEntryPoint(new AuthenticationEntryPoint() {
                        @Override
                        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                            if ("XMLHttpRequest".equalsIgnoreCase(request.getHeader("x-requested-with")) || true) {
                                log.info("commence");

                                response.setCharacterEncoding("utf-8");
                                response.setHeader("Access-Control-Allow-Origin", "*");
                                response.setHeader("Access-Control-Allow-Headers",
                                        "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
                                response.setContentType("application/json; charset=utf-8");
                                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                            }
                        }
                    })
                    .accessDeniedHandler(new AccessDeniedHandler() {
                        @Override
                        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                            response.setCharacterEncoding("utf-8");
                            response.setHeader("Access-Control-Allow-Origin", "*");
                            response.setHeader("Access-Control-Allow-Headers",
                                    "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
                            response.setContentType("application/json; charset=utf-8");
                            response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                        }
                    })
                    .and()
                    .formLogin()
                    .usernameParameter("username").passwordParameter("password")
                    .loginProcessingUrl("/login")
                    .successHandler(new SimpleUrlAuthenticationSuccessHandler() {
                        @Override
                        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                            response.setCharacterEncoding("utf-8");
                            response.setHeader("Access-Control-Allow-Origin", "*");
                            response.setHeader("Access-Control-Allow-Headers",
                                    "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
                            response.setContentType("application/json; charset=utf-8");
                            response.setStatus(HttpServletResponse.SC_OK);
                        }
                    })
                    .failureHandler(new SimpleUrlAuthenticationFailureHandler() {
                        @Override
                        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                            response.setCharacterEncoding("utf-8");
                            response.setHeader("Access-Control-Allow-Origin", "*");
                            response.setHeader("Access-Control-Allow-Headers",
                                    "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
                            response.setContentType("application/json; charset=utf-8");
                            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                        }
                    })
                    .and()
                    .logout()
                    .permitAll()
                    .logoutSuccessHandler(new LogoutSuccessHandler() {
                        @Override
                        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                            log.info("=========logout");
                            response.setCharacterEncoding("utf-8");
                            response.setHeader("Access-Control-Allow-Origin", "*");
                            response.setHeader("Access-Control-Allow-Headers",
                                    "token, Accept, Origin, X-Requested-With, Content-Type, Last-Modified");
                            response.setContentType("application/json; charset=utf-8");
                            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                        }
                    })
                    .and()
                    .csrf().disable();
        }

        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(uds).passwordEncoder(new BCryptPasswordEncoder());
        }
    }
}

@Service
class MyUserDetailsService implements UserDetailsService {
    @Autowired
    @Lazy
    private EndUserDao endUserDao;
    
    @Autowired
    @Lazy
    private WorkFlowService workFlowService;
    

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        EndUser endUser = this.endUserDao.findOne(new Specification<EndUser>() {
            private static final long serialVersionUID = 1L;

            @Override
            public Predicate toPredicate(Root<EndUser> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                root.fetch("roles", JoinType.INNER);
                
                List<Predicate> predicates = new ArrayList<Predicate>();
                predicates.add(criteriaBuilder.equal(root.get("username").as(String.class), username));
                return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
            }
        }).orElse(new EndUser());

        return endUser;
    }
}
